package cz.possoft.explorer.resolver;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import cz.possoft.explorer.context.FieldModifierEnum;

/**
 * Resolver is using fields of particular class to resolver values. It is possible to resolve also private fields
 * thanks to reflection.
 * 
 * @author <a href="mailto:mposolda@gmail.com">Marek Posolda</a>
 * @version $Revision$
 */
public class FieldResolverImpl<V> implements Resolver<V, String>
{
   
   /**
    * Get names of all fields of this class.<br>
    * - Don't count static fields but count all other public,default,protected and private fields.<br>
    * 
    * @param clazz parameter is used to obtain names of fields
    * @param object - This parameter is useless in FieldResolverImpl implementation 
    */
   @Override
   public Map<String, Class<?>> getFields(Class<? super V> clazz, V object)
   {
      Map<String, Class<?>> resultMap = new HashMap<String, Class<?>>();
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields)
      {
         if (Modifier.isStatic(field.getModifiers()))
         {
            continue;
         }
         
         resultMap.put(field.getName(), field.getType());
      }
      
      return resultMap;
   }
   
   /**
    * Get value of field on given object.
    */
   public Object getFieldValue(V object, String fieldName)
   {
      try
      {
         Field field = findField(object, fieldName);
         
         // set accessible so that we can have values of private fields as well
         field.setAccessible(true);
         
         // now return result.
         return field.get(object);
      }
      catch (Exception e)
      {
         throw new RuntimeException("Can't obtain value of field " + fieldName + " on object " + object, e);
      }
   }
   
   /**
    * @return modifier for specified field
    */
   public FieldModifierEnum getFieldModifier(V object, String fieldName)
   {
      try
      {
         Field field = findField(object, fieldName);
                
         // create field modifier from given modifiers of our field.
         return FieldModifierEnum.create(field.getModifiers());
      }
      catch (Exception e)
      {
         throw new RuntimeException("Can't obtain value of field " + fieldName + " on object " + object, e);
      }      
   }
   
   public Class<String> getKeyClass()
   {
      return String.class;
   } 
   
   /**
    * We are trying to find field in current class or in superclasses.
    * 
    * @param object
    * @param fieldName
    * @return founded field on class of object V or some superclass of it.
    */
   private Field findField(V object, String fieldName)
   {
      Field result = null;
      Class<?> clazz = object.getClass();
      while (clazz != null)
      {
         try
         {
            result = clazz.getDeclaredField(fieldName);
         }
         catch (NoSuchFieldException ignore)
         {
            // We are ignoring this exception, because we need to look at the top of algorithm
         }
         
         if (result != null)
         {
            return result;
         }
         
         clazz = clazz.getSuperclass();
      }
    
      return result;
   }

}

